unit database_enemies;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls,
  Buttons, Spin, Menus, database_base, itemselectorframeunit, fpjson,
  controlhelper, traitlist, actionpatternsframeunit, dropitemselection;

type

  { TDatabaseEnemiesFrame }

  TDatabaseEnemiesFrame = class(TDatabaseBaseFrame)
    EnemyActionPatternsFrame: TActionPatternsFrame;
    ActionPatternsGroupBox: TGroupBox;
    NotesMemo: TMemo;
    NotesGroupBox: TGroupBox;
    EnemyTraitListFrame: TTraitListFrame;
    TraitsNotesSplitter: TSplitter;
    TraitsGroupBox: TGroupBox;
    ItemDrop1Button: TButton;
    EnemySelectorFrame: TItemSelectorFrame;
    ContentScrollBox: TScrollBox;
    ExpLabel: TLabel;
    GoldLabel: TLabel;
    ItemDrop2Button: TButton;
    ItemDrop3Button: TButton;
    ItemDropsLabel: TLabel;
    NumericSpoilsPanel: TPanel;
    ExpSpinEdit: TSpinEdit;
    GoldSpinEdit: TSpinEdit;
    ItemDropsPanel: TPanel;
    AdditionalDataPanel: TPanel;
    SpoilsGroupBox: TGroupBox;
    ParamsFlowPanel: TFlowPanel;
    GeneralSettingsGroupBox: TGroupBox;
    MainDataPanel: TPanel;
    NameEdit: TEdit;
    NameLabel: TLabel;
    ImageLabel: TLabel;
    NamePanel: TPanel;
    ImagePanel: TPanel;
    SelectorContentSplitter: TSplitter;
    ImageSpeedButton: TSpeedButton;
    procedure ExpSpinEditChange(Sender: TObject);
    procedure GoldSpinEditChange(Sender: TObject);
    procedure ImageSpeedButtonClick(Sender: TObject);
    procedure Init; override;
    procedure ItemDropButtonClick(Sender: TObject);
    procedure NameEditChange(Sender: TObject);
    procedure NotesMemoChange(Sender: TObject);
  private
    IsLoading: Boolean;
    SelectedId: Integer;
    ParamPanels: TParamPanels;
    procedure CreateParamPanels;
    procedure LoadGeneralSettings(Enemy: TJSONObject);
    procedure LoadImage(Enemy: TJSONObject);
    procedure LoadSpoils(Enemy: TJSONObject);
    procedure LoadDropItems(Enemy: TJSONObject);
    procedure LoadActionPatterns(Enemy: TJSONObject);
    procedure LoadAdditionalData(Enemy: TJSONObject);
    function GetSelectedEnemy: TJSONObject;
    function FormatDropItem(Drop: TJSONObject): String;
  public
    procedure LoadEnemyData(Id: Integer);
    procedure UpdateLanguage(Code: String); override;
  end;

resourcestring
  EnemiesHeader = 'Enemies';
  NoDropItems = 'No item';
  UnknownDropItem = '(Unknown item type)';

implementation

uses
  globals, gameproject, languagehelper, imageselection,
  constants;

{$R *.lfm}

{ TDatabaseEnemiesFrame }

procedure TDatabaseEnemiesFrame.Init;
begin
  IsLoading := True;
  EnemySelectorFrame.SetData(Db.Enemies, @Db.ResizeEnemies, @LoadEnemyData);
  EnemySelectorFrame.Title := EnemiesHeader;
  EnemySelectorFrame.JsonDataType := 'application/rpgmv-enemies';
  EnemySelectorFrame.CreateElementCallback := @Db.CreateEmptyEnemy;
  CreateParamPanels;

  EnemyTraitListFrame.SetDatabase(Db);
  EnemyActionPatternsFrame.SetDatabase(Db);

  LoadEnemyData(1);
end;

procedure TDatabaseEnemiesFrame.ItemDropButtonClick(Sender: TObject);
var
  Btn: TButton;
  DropIndex: Integer;
  Enemy: TJSONObject = nil;
  Drops: TJSONArray = nil;
  Drop: TJSONObject = nil;
  Kind: Integer = 0;
  DataId: Integer = 1;
  Denominator: Integer = 1;
  KindJson, DataIdJson, DenominatorJson: TJSONNumber;
begin
  Btn := Sender as TButton;
  DropIndex := Btn.Tag;

  Enemy := GetSelectedEnemy;
  if (Enemy <> nil) then
    Enemy.Find('dropItems', Drops);
  if (Drops <> nil) and (DropIndex >= 0) and (DropIndex <= 2) then
    Drop := Drops.Objects[DropIndex];

  if (Drop <> nil) and Drop.Find('kind', KindJson) then
    Kind := KindJson.AsInteger;
  if (Drop <> nil) and Drop.Find('dataId', DataIdJson) then
    DataId := DataIdJson.AsInteger;
  if (Drop <> nil) and Drop.Find('denominator', DenominatorJson) then
    Denominator := DenominatorJson.AsInteger;

  if DropItemSelectionForm.ShowSelection(Kind, DataId, Denominator, Db) or True then begin
    if Drop <> nil then begin
      Drop.Integers['kind'] := DropItemSelectionForm.SelectedKind;
      Drop.Integers['dataId'] := DropItemSelectionForm.SelectedDataId;
      Drop.Integers['denominator'] := DropItemSelectionForm.SelectedDenominator;

      LoadDropItems(Enemy);
    end;
  end;
end;

procedure TDatabaseEnemiesFrame.NameEditChange(Sender: TObject);
var
  Enemy: TJSONObject = nil;
begin
  if not IsLoading then
    Enemy := GetSelectedEnemy;

  if Enemy <> nil then
    Enemy.Strings['name'] := NameEdit.Text;

  EnemySelectorFrame.RefreshSelectedName;
end;

procedure TDatabaseEnemiesFrame.NotesMemoChange(Sender: TObject);
var
  Enemy: TJSONObject = nil;
begin
  if not IsLoading then
    Enemy := GetSelectedEnemy;

  if Enemy <> nil then
    Enemy.Strings['note'] := NotesMemo.Text;
end;

procedure TDatabaseEnemiesFrame.ImageSpeedButtonClick(Sender: TObject);
var
  Enemy: TJSONObject = nil;
  Image: String = '';
  Hue: Integer = 0;
  ImageJson: TJSONString;
  HueJson: TJSONNumber;
begin
  Enemy := GetSelectedEnemy;
  if (Enemy <> nil) and Enemy.Find('battlerName', ImageJson) then
    Image := ImageJson.AsString;
  if (Enemy <> nil) and Enemy.Find('battlerHue', HueJson) then
    Hue := HueJson.AsInteger;

  if ImageSelectionForm.ShowEnemySelection(Image, Hue, Db.IsSvBattle) then begin
    Enemy.Strings['battlerName'] := ImageSelectionForm.SelectedFilename;
    Enemy.Integers['battlerHue'] := ImageSelectionForm.SelectedHue;
    LoadImage(Enemy);
  end;
end;

procedure TDatabaseEnemiesFrame.ExpSpinEditChange(Sender: TObject);
var
  Enemy: TJSONObject = nil;
begin
  if not IsLoading then
    Enemy := GetSelectedEnemy;

  if Enemy <> nil then
    Enemy.Integers['exp'] := ExpSpinEdit.Value;
end;

procedure TDatabaseEnemiesFrame.GoldSpinEditChange(Sender: TObject);
var
  Enemy: TJSONObject = nil;
begin
  if not IsLoading then
    Enemy := GetSelectedEnemy;

  if Enemy <> nil then
    Enemy.Integers['gold'] := GoldSpinEdit.Value;
end;

procedure TDatabaseEnemiesFrame.CreateParamPanels;
var
  I: Integer;
begin
  ParamPanels := TParamPanels.Create(Self, ParamsFlowPanel, Db);
  ParamPanels.ValuesAreAbsolute := True;

  for I := 0 to 7 do
    ParamPanels.CreatePanel(I);

  ParamsFlowPanel.AutoSize := True;
end;

procedure TDatabaseEnemiesFrame.LoadGeneralSettings(Enemy: TJSONObject);
var
  EnemyName: String = '';
  NameJson: TJSONString;
  Params: TJSONArray = nil;
begin
  if (Enemy <> nil) and Enemy.Find('name', NameJson) then
    EnemyName := NameJson.AsString;
  NameEdit.Text := EnemyName;

  LoadImage(Enemy);

  if (Enemy <> nil) then
    Enemy.Find('params', Params);
  ParamPanels.SetParams(Params);
end;

procedure TDatabaseEnemiesFrame.LoadImage(Enemy: TJSONObject);
var
  Image: String = '';
  Hue: Integer = 0;
  ImageJson: TJSONString;
  HueJson: TJSONNumber;
  BattleStyle: TBattleStyle = bsFrontView;
  EnemyImage: TBitmap;
begin
  if (Enemy <> nil) and Enemy.Find('battlerName', ImageJson) then
    Image := ImageJson.AsString;
  if (Enemy <> nil) and Enemy.Find('battlerHue', HueJson) then
    Hue := HueJson.AsInteger;
  if Db.IsSvBattle then
    BattleStyle := bsSideView;

  if Image <> '' then begin
    EnemyImage := Game.GetEnemyGraphics(Image, Hue, 160, 160, BattleStyle);
    try
      ImageSpeedButton.Glyph.Assign(EnemyImage);
    finally
      EnemyImage.Free;
    end;
  end else
    ImageSpeedButton.Glyph.Clear;
end;

procedure TDatabaseEnemiesFrame.LoadSpoils(Enemy: TJSONObject);
var
  Gold: Integer = 0;
  Exp: Integer = 0;
  GoldJson, ExpJson: TJSONNumber;
begin
  if (Enemy <> nil) and Enemy.Find('gold', GoldJson) then
    Gold := GoldJson.AsInteger;
  GoldSpinEdit.Value := Gold;

  if (Enemy <> nil) and Enemy.Find('exp', ExpJson) then
    Exp := ExpJson.AsInteger;
  ExpSpinEdit.Value := Exp;

  LoadDropItems(Enemy);
end;

procedure TDatabaseEnemiesFrame.LoadDropItems(Enemy: TJSONObject);
var
  DropItems: TJSONArray = nil;

  procedure SetDropItemBtn(Btn: TButton);
  var
    Index: Integer;
  begin
    Index := Btn.Tag;
    if (DropItems <> nil) and (Index >= 0) and (Index < DropItems.Count) then
      Btn.Caption := FormatDropItem(DropItems.Objects[Index])
    else
      Btn.Caption := '';
  end;

begin
  if Enemy <> nil then
    Enemy.Find('dropItems', DropItems);

  SetDropItemBtn(ItemDrop1Button);
  SetDropItemBtn(ItemDrop2Button);
  SetDropItemBtn(ItemDrop3Button);
end;

procedure TDatabaseEnemiesFrame.LoadActionPatterns(Enemy: TJSONObject);
var
  Patterns: TJSONArray;
begin
  if (Enemy <> nil) and Enemy.Find('actions', Patterns) then
    EnemyActionPatternsFrame.SetPatterns(Patterns)
  else
    EnemyActionPatternsFrame.SetPatterns(nil)
end;

procedure TDatabaseEnemiesFrame.LoadAdditionalData(Enemy: TJSONObject);
var
  Notes: String = '';
  NotesJson: TJSONString;
  Traits: TJSONArray;
begin
  if (Enemy <> nil) and Enemy.Find('traits', Traits) then
    EnemyTraitListFrame.SetTraitList(Traits)
  else
    EnemyTraitListFrame.SetTraitList(nil);

  if (Enemy <> nil) and Enemy.Find('note', NotesJson) then
    Notes := NotesJson.AsString;
  NotesMemo.Text := Notes;
end;

function TDatabaseEnemiesFrame.GetSelectedEnemy: TJSONObject;
begin
  GetSelectedEnemy := nil;
  if Db.Enemies <> nil then
    if (SelectedId >= 1) and (SelectedId <= Db.Enemies.Count -1) then
      GetSelectedEnemy := Db.Enemies.Objects[SelectedId];
end;

function TDatabaseEnemiesFrame.FormatDropItem(Drop: TJSONObject): String;
var
  Kind: Integer = 0;
  DataId: Integer = 0;
  Denominator: Integer = 0;
  KindJson, DataIdJson, DenominatorJson: TJSONNumber;

  function WithDenominator(Name: String): String;
  begin
    { TODO: check this works well with bidi. Maybe make this translatable? }
    WithDenominator := Name + ' 1/' + IntToStr(Denominator);
  end;

begin
  if (Drop <> nil) and Drop.Find('kind', KindJson) then
    Kind := KindJson.AsInteger;
  if (Drop <> nil) and Drop.Find('dataId', DataIdJson) then
    DataId := DataIdJson.AsInteger;
  if (Drop <> nil) and Drop.Find('denominator', DenominatorJson) then
    Denominator := DenominatorJson.AsInteger;

  case Kind of
    DROP_ITEM_KIND_NONE:
      FormatDropItem := NoDropItems;
    DROP_ITEM_KIND_ITEM:
      FormatDropItem := WithDenominator(Db.GetItemName(DataId));
    DROP_ITEM_KIND_WEAPON:
      FormatDropItem := WithDenominator(Db.GetWeaponName(DataId));
    DROP_ITEM_KIND_ARMOUR:
      FormatDropItem := WithDenominator(Db.GetArmourName(DataId));
  else
    FormatDropItem := UnknownDropItem;
  end;
end;

procedure TDatabaseEnemiesFrame.LoadEnemyData(Id: Integer);
var
  Enemy: TJSONObject;
begin
  IsLoading := True;
  SelectedId := Id;
  Enemy := GetSelectedEnemy;
  LoadGeneralSettings(Enemy);
  LoadSpoils(Enemy);
  LoadActionPatterns(Enemy);
  LoadAdditionalData(Enemy);

  IsLoading := False;
end;

procedure TDatabaseEnemiesFrame.UpdateLanguage(Code: String);
begin
  inherited UpdateLanguage(Code);
  if IsLanguageRtl(Code) then begin
    ParamsFlowPanel.FlowStyle := fsRightLeftTopBottom;
  end else begin
    ParamsFlowPanel.FlowStyle := fsLeftRightTopBottom;
  end;
end;

end.

